<?php
/**
 * File iControl.php
 *
 * File containing the iControl class
 *
 * This file is part of Royal project
 *
 * PHP versions 5
 *
 * @copyright Copyright (c) 2010, iLogos
 *
 */

  /**
   * iControl
   *
   * The iControl class provides security logic for calling method and functions.
   *
   * @copyright Copyright (c) 2010, iLogos
   *
   */
  class iControl implements iSingleton {

    /**
     * instance of iConfiguration object
     *
     * @var iConfiguration
     */
    private static $_instance;

    /**
     * Array holding the controls for classes methods
     * @var array
     * @access protected
     */
    protected static $_controls = array();

    /**
     * Array holding the control modules
     * @var array
     * @access protected
     */
    protected static $_modules = array();

    /**
     * Array holding maxim count of error but in can't be more thar 4
     * @var array
     * @access protected
     */
    public static $max_recursive = 0;


    /**
     * Implementing Singleton pattern
     *
     * @return iControl object instance of the class
     *
     * @access public
     */
    public static function getInstance($param = false){
      if (!isset(self::$_instance)){
        self::$_instance = new iControl($param);
      }
      return self::$_instance;
    }

    private function __construct() {
      $iDatabase = iFactory::singleton('iDatabase_'.DB_DATABASE_CLASS);
      /**
       * Load all the application parameters and define them
       */
      $Qcontrols = $iDatabase->Query('SELECT
                                             class_name,
                                             method_name,
                                             AES_DECRYPT(params, @db_key) as params
                                           FROM :table_control', 1800);
      $Qcontrols->bindTable(':table_control', TABLE_CONTROL);
      $Qcontrols->execute();

      if ($Qcontrols->numberOfRows() > 0) {
        while($Qcontrols->next()) {
          self::$_controls[$Qcontrols->value('class_name')][$Qcontrols->value('method_name')] = unserialize($Qcontrols->value('params'));
        }
      }
      $Qcontrols->freeResult();
    }

    /**
     *
     */
    private static function initControlModules() {

      /*
       * Find All Modules
       */
      foreach (iFactory::getClasses() as $classname) {
        if (strpos($classname, 'imodulescontrol') !== false) {
          if ($classname != 'imodulescontrol') {
            self::$_modules[$classname] = $classname;
          }
        }
      }

      /*
       * Get Modules Errors Types
       */
      foreach (self::$_modules as $classname) {
        $module = iFactory::create($classname);
        self::$_modules[$classname] = $module->error_type;
      }

    }

    /**
     * checkArray
     *
     * Function that checked function params with control etalon
     *
     * @param array $control
     * @param array $params
     *
     * @return bool
     */
    private static function checkArray($control, $params) {

      $result = true;
      $func_args = func_get_args();

      //check count of array keys
      if (count($control) != count($params)) {
        self::ThreatProcessor(CET_ERROR, 'C_FAIL_COUNT_CONTROL_PARAMS', __FILE__, __CLASS__, __FUNCTION__, __LINE__, 'Wrong count - iControl:' . count($control) . '  Input:' . count($params), serialize($func_args));
        return false;
      }

      foreach ($control as $key => $value) {
        //If field contailn reference key, than this is min level
        if (is_array($control[$key]) && array_key_exists('reference', $control[$key]) && count($control[$key]['reference']) > 0) {
          if (!array_key_exists($key, $params)) {
            self::ThreatProcessor(CET_ERROR, 'C_FAIL_MISSED_INPUT_IN_PARAMS', __FILE__, __CLASS__, __FUNCTION__, __LINE__, 'ERROR - Field not exist: ' . $key, serialize($func_args));
            $result = false;
            break;
          }

          if (is_array($params[$key])) {
            self::ThreatProcessor(CET_ERROR, 'C_WRONG_TYPE_INPUT_PARAMS', __FILE__, __CLASS__, __FUNCTION__, __LINE__, 'ERROR - Field is array: ' . $key, serialize($func_args));
            $result = false;
            break;
          }

          $check_result = false;
          if (!is_array($control[$key]['reference'])) {
            $control[$key]['reference'] = array($control[$key]['reference']);
          }
          foreach ($control[$key]['reference'] as $refference) {
            switch ($refference[0]) {
              case 'p':
                $pattern = substr($refference, 1);
                if (preg_match($pattern, $params[$key]) == 0) {
                } else {
                 $check_result = true; break 2;
                }
                break;
              case '*':
                $check_result = true; break 2;
                break;
              case 'c':
                if (substr($refference, 1) != $params[$key]) {
                } else {
                 $check_result = true; break 2;
                }
                break;
              case 'n':
                if (!is_null($params[$key])) {
                } else {
                  $check_result = true; break 2;
                }
                break;
            }
          }
          $result = $check_result;
          if (!$result) {
            self::ThreatProcessor(CET_ERROR, 'C_CHECK_BAD_KEY', __FILE__, __CLASS__, __FUNCTION__, __LINE__, 'Bad key: ' . $key, serialize($func_args));
            break;
          }

        }

        if (is_array($control[$key]) && count($control[$key]) > 0 && !isset($control[$key]['reference'])) {
          if (isset($params[$key]) && is_array($params[$key]) && count($params[$key]) > 0) {
            $res = self::checkArray($control[$key], $params[$key]);
            if (!$res) {
              self::ThreatProcessor(CET_ERROR, 'C_WRON_RECURSIVE_KEY', __FILE__, __CLASS__, __FUNCTION__, __LINE__, 'ERROR - Wrong rec key: ' . $key, serialize($func_args));
              $result = false;
              break;
            }
          } else {
            self::ThreatProcessor(CET_ERROR, 'C_WRON_KEY', __FILE__, __CLASS__, __FUNCTION__, __LINE__, 'ERROR - Wrong key: ' . $key, serialize($func_args));
            $result = false;
            break;
          }
        }

      }
      return $result;
    }

    /**
     * checkParams
     *
     * Function that check params for some class methods
     *
     * @param string $class - class name
     * @param string $method - method name
     * @param array $params - function input parameters
     * @access public
     */
    public static function checkParams($class = null, $method = null, $params = null) {

      $func_args = func_get_args();

      /*
       * Check Empty input parameters
       */
      if (is_null($class) || is_null($method) || is_null($params)) {
        self::ThreatProcessor(CET_ERROR, 'C_NULLED_CHECK_PARAMS', __FILE__, __CLASS__, __FUNCTION__, __LINE__, 'Null call - ' . (isset($func_args[0])?$func_args[0]:'NULL') . '::' . (isset($func_args[1])?$func_args[1]:'NULL'), serialize($func_args));
       // exit;
      }
      $class  = md5($class);
      $method = md5($method);
      if (isset(self::$_controls[$class]) && isset(self::$_controls[$class][$method])) {
        $control_array = self::$_controls[$class][$method];
        if (count($control_array) != count($params)) {
          self::ThreatProcessor(CET_ERROR, 'C_WRONG_COUNT_PARAMS', __FILE__, __CLASS__, __FUNCTION__, __LINE__, 'Wrong function call', serialize($func_args));
         // exit ;
        }

        $res = self::checkArray($control_array, $params);

        if (!$res) {
          self::ThreatProcessor(CET_ERROR, 'C_FAIL_CHECK_PARAMS', __FILE__, __CLASS__, __FUNCTION__, __LINE__, 'Fail checked params for call function - ' . (isset($func_args[0])?$func_args[0]:'NULL') . '::' . (isset($func_args[1])?$func_args[1]:'NULL'), serialize($func_args));
         // exit ;
        } else {
          return true;
        }
      } else {
        self::ThreatProcessor(CET_ERROR, 'C_CALL_NOT_EXITS_METHOD_OR_CLASS', __FILE__, __CLASS__, __FUNCTION__, __LINE__, 'Call undefined method - ' . (isset($func_args[0])?$func_args[0]:'NULL') . '::' . (isset($func_args[1])?$func_args[1]:'NULL'), serialize($func_args));
        // exit ;
      }

      //TODO - need logic for this exit
      exit ;
    }

    /**
     * getParams
     *
     * Function for get control array for some class and methods
     *
     * @param string $class - class name
     * @param string $method - method name
     * @return array
     */
    public static function getParams($class, $method){
      if (isset(self::$_controls[md5($class)]) && isset(self::$_controls[md5($class)][md5($method)])) {
        return self::$_controls[md5($class)][md5($method)];
      } else {
        return null;
      }
    }

    /**
     * setParams
     *
     * Function for set configuration value
     *
     * @param string $class - class name
     * @param string $method - method name
     * @param string $params - control array of parameters for class method
     * @return bool
     */
    public static function setParams($class, $method, $params){

      $iDatabase = iFactory::singleton('iDatabase_' . DB_DATABASE_CLASS);

      $Qupdate = $iDatabase->query('INSERT INTO :table_control (
                                      `class_name`,
                                      `method_name`,
                                      `params`)
                                    VALUES (
                                      :class_name,
                                      :method_name,
                                      AES_ENCRYPT(UNCOMPRESS(:params), @db_key))
                                    ON DUPLICATE KEY UPDATE
                                      `params` = VALUES(`params`)');
      $Qupdate->bindTable(':table_control', TABLE_CONTROL);
      $Qupdate->bindValue(':class_name',  md5($class));
      $Qupdate->bindValue(':method_name', md5($method));
      $Qupdate->bindAES(':params',        serialize($params));
      $Qupdate->execute();

      if (!$iDatabase->isError()) {
        self::$_controls[$class][$method] = $params;
        $iDatabase->clearFileCache();
      }

      return !$iDatabase->isError();
    }


    public static function getErrorCodeRules($errorCode = '', $page = -1, $page_limit = 1) {

      $iDatabase = iFactory::singleton('iDatabase_' . DB_DATABASE_CLASS);

      $Qselect = $iDatabase->query('SELECT
                                      `idControlRulesErrorCodes`,
                                      `idControlRulesTypes`,
                                      `value`
                                    FROM :table_control_rules');
      $Qselect->bindTable(':table_control_rules',    TABLE_CONTROL_RULES);
      $Qselect->execute();

      $result = array();
      if ($Qselect->numberOfRows() > 0) {
        while ($Qselect->next()) {
          $tmp = $Qselect->toArray();
          if (!is_null($tmp['value'])) {
            $tmp['value'] = unserialize($tmp['value']);
          } else {
            $tmp['value'] = null;
          }
          $result[] = $tmp;
        }
      }
      return $result;
    }

    /**
     * setErrorCodeRules
     *
     * Function that set action for errors
     *
     * @param int $idControlRulesErrorCode - id of error code
     * @param int $idControlRulesType - id of rules types
     * @param mixed $value - value for action
     */
    public static function setErrorCodeRules($idControlRulesErrorCode = null, $idControlRulesType = null, $value) {

/*
      iControl::setParams(__CLASS__, __FUNCTION__, array('0' => array('reference' => 'p/^[0-9]+$/'),
                                                         '1' => array('reference' => 'p/^[0-9]+$/'),
                                                         '2' => array()
                          ));/**/
      $func = func_get_args();
      iControl::checkParams(__CLASS__, __FUNCTION__, $func);

      if (is_null($idControlRulesErrorCode) || is_null($idControlRulesType)) {
        return false;
      }
      $iDatabase = iFactory::singleton('iDatabase_' . DB_DATABASE_CLASS);

      $Qupdate = $iDatabase->query('INSERT INTO :table_control_rules (
                                      `idControlRulesErrorCodes`,
                                      `idControlRulesTypes`,
                                      `value`
                                    ) VALUES (
                                      :idControlRulesErrorCodes,
                                      :idControlRulesTypes,
                                      :value)
                                    ON DUPLICATE KEY UPDATE
                                      `value` = VALUES(`value`)');
      $Qupdate->bindTable(':table_control_rules', TABLE_CONTROL_RULES);
      $Qupdate->bindInt(':idControlRulesErrorCodes', $idControlRulesErrorCode);
      $Qupdate->bindInt(':idControlRulesTypes',      $idControlRulesType);
      $Qupdate->bindValue(':value',                  serialize($value));
      $Qupdate->execute();

      return !$iDatabase->isError();
    }

    /**
     * ThreatProcessor
     *
     * Function that processed All errors
     *
     * @param string $errorType - error type
     * @param string $errorCode - unique error code
     * @param string $file      - file name where the exception was thrown
     * @param string $class     - class name where the exception was thrown
     * @param string $function  - method name where the exception was thrown
     * @param string $line      - line where the exception was thrown
     * @param string $errorText - error text
     * @param string $data      - serialized method parameters
     */
    public static function ThreatProcessor($errorType, $errorCode = null, $file, $class, $function, $line, $errorText, $data) {

      /*
       * Debug
       */
//spr($errorType . '::' . $errorCode . ' --> ' . $errorText);
lDebug::save("\r\nFILE: " . $file .
             "\r\nLINE: " . $line .
             "\r\nERROR: " . $errorType . '::' . $errorCode . ' --> ' . $errorText);

      if (strpos($errorCode, 'SQL') !== false) {
        iLog::add($errorText, 'error', null, null, true);
        return false;
      }

      static $running = 0;

      if ($running > self::$max_recursive || $running > 4) {
        iLog::add('Fatal error: Errors repeat recursive');
        die();
      }
      $running += 1;

      /*
       * Dump all variables
       */
      $DataForSave['GET']     = $_GET;
      $DataForSave['POST']    = $_POST;
      $DataForSave['ENV']     = $_ENV;
      $DataForSave['SERVER']  = $_SERVER;
      if (isset($_SESSION)) {
        $DataForSave['SESSION'] = $_SESSION;
      }
      $DataForSave['GLOBALS'] = $GLOBALS;
      $DataForSave['CONTROL_PARAMS'] = func_get_args();

      /*
       * Initialize modules
       */
      self::initControlModules();

      /*
       * Detect process module
       */
      $module = array_search($errorType, self::$_modules);
      if ($module === false) {
        $module = 'unknown';
      }

      //All params without errorType(its filename)
      $actions = iFactory::call($module, 'getActions', $errorCode, $file, $class, $function, $line, $errorText, $data, $DataForSave);

      foreach ($actions as $action_code => $action) {
        switch ($action_code) {
          case 'block_customer':
            if ($action) {
              $iDatabase = iFactory::singleton('iDatabase_' . DB_DATABASE_CLASS);

              $QSetStatus = $iDatabase->query('UPDATE :table_customers (
                                               SET idCustomersStatuses = :idStatus
                                               WHERE id = :idCustomer');
              $QSetStatus->bindTable(':table_customers', TABLE_CUSTOMERS);
              $QSetStatus->bindInt(':idStatus',   $action);
              //TODO idCustomer!
              $QSetStatus->bindInt(':idCustomer', $idCustomer);
              $QSetStatus->execute();
            }
            break;
          case 'block_manager':
            if ($action) {
              $iDatabase = iFactory::singleton('iDatabase_' . DB_DATABASE_CLASS);

              $QSetStatus = $iDatabase->query('UPDATE :table_managers (
                                               SET idCustomersStatuses = :idStatus
                                               WHERE id = :idManager');
              $QSetStatus->bindTable(':table_managers', TABLE_MANAGERS);
              $QSetStatus->bindInt(':idStatus',  $action);
              //TODO idManager!
              $QSetStatus->bindInt(':idManager', $idManager);
              $QSetStatus->execute();
            }
            break;
          case 'block_partner':
            if ($action) {
              $iDatabase = iFactory::singleton('iDatabase_' . DB_DATABASE_CLASS);

              $QSetStatus = $iDatabase->query('UPDATE :table_partners (
                                               SET idCustomersStatuses = :idStatus
                                               WHERE id = :idPartner');
              $QSetStatus->bindTable(':table_partners', TABLE_PARTNERS);
              $QSetStatus->bindInt(':idStatus',  $action);
              //TODO idPartner!
              $QSetStatus->bindInt(':idPartner', $idPartner);
              $QSetStatus->execute();
            }
            break;
          case 'block_broker':
            if ($action) {
              $iDatabase = iFactory::singleton('iDatabase_' . DB_DATABASE_CLASS);

              $QSetStatus = $iDatabase->query('UPDATE :table_brokers (
                                               SET active = 0
                                               WHERE id = :idBroker');
              $QSetStatus->bindTable(':table_brokers', TABLE_BROKERS);
              $QSetStatus->bindInt(':idBroker', $GLOBALS['brokerData']['id']);
              $QSetStatus->execute();

              $iDatabase->clearFileCache();
            }
            break;
          case 'block_ip':
            if ($action) {
              $iDatabase = iFactory::singleton('iDatabase_' . DB_DATABASE_CLASS);

              $QBlockIp = $iDatabase->query('INSERT INTO :table_brokers_ips (
                                              ip,
                                              blocked,
                                              added,
                                              updated)
                                            VALUES (
                                              :ip,
                                              1,
                                              :added,
                                              NOW())
                                            ON DUPLICATE KEY UPDATE
                                              blocked = 1');
              $QBlockIp->bindTable(':table_brokers_ips', TABLE_BROKERS_IPS);
              $QBlockIp->bindValue(':ip',     $_SERVER['REMOTE_ADDR']);
              $QBlockIp->bindFloat(':added',  microtime(1));
              $QBlockIp->execute();
            }
            break;
          case 'block_class':
            if ($action) {
              if (!empty($class)) {
                $iDatabase = iFactory::singleton('iDatabase_' . DB_DATABASE_CLASS);

                $QBlockClass = $iDatabase->query('INSERT INTO :table_blocked_classes (
                                                    className,
                                                    reason,
                                                    added,
                                                    updated)
                                                  VALUES (
                                                    :className,
                                                    :reason,
                                                    :added,
                                                    NOW())
                                                  ON DUPLICATE KEY UPDATE
                                                    ip     = VALUES(ip),
                                                    reason = VALUES(reason)');
                $QBlockClass->bindTable(':table_blocked_classes', TABLE_BLOCKED_CLASSES);
                $QBlockClass->bindValue(':className', strtolower($class));
                $QBlockClass->bindValue(':reason',    $errorText);
                $QBlockClass->bindFloat(':added',     microtime(1));
                $QBlockClass->execute();

                $iDatabase->clearFileCache();

              } else {
                iLog::add('Try block empty class', 'error');
              }
            }
            break;
          case 'block_all':
            if ($action) {
              $iDatabase = iFactory::singleton('iDatabase_' . DB_DATABASE_CLASS);

              $QblockAll = $iDatabase->query('INSERT INTO :table_brokers_ips (
                                                ip,
                                                blocked,
                                                added,
                                                updated)
                                              VALUES (
                                                \'*.*.*.*\',
                                                1,
                                                :added,
                                                NOW())
                                              ON DUPLICATE KEY UPDATE
                                                blocked = 1');
              $QblockAll->bindTable(':table_brokers_ips', TABLE_BROKERS_IPS);
              $QblockAll->bindValue(':ip',     $_SERVER['REMOTE_ADDR']);
              $QblockAll->bindFloat(':added',  microtime(1));
              $QblockAll->execute();
            }
            break;
          case 'send_email_queque':
            if ($action) {
              iFactory::call('iEmail', 'queueEmail', $action);
            }
            break;
          case 'send_email_direct':
            if ($action) {
              $mail = iFactory::create('iMail', CRASH_EMAIL_TO_NAME,
                                                CRASH_EMAIL_TO_EMAIL,
                                                CRASH_EMAIL_FROM_NAME,
                                                CRASH_EMAIL_FROM_EMAIL,
                                                'ERROR Message - Type[' . $errorType . '] Code[' . $errorCode . ']');

              $mail->setBodyPlain('Error in the file: ' . $file . ' on line - ' . $line . "\n" .
                                   $errorText  . "\n" .
                                   (!empty($class)?'Class:' . $class:'') . "\n" .
                                   (!empty($function)?'Method:' . $function:'') . "\n");
              $mail->send();
            }
            break;
          case 'add_log':
            if ($action) {
              iLog::add($errorText, ($errorType == CET_ERROR?'error':'warning'), null, null, true);
            }
            break;
        }
      }

      $running -= 1;
      if ($running < 0) $running = 0;
      if (isset($actions['continue code execution']) && $actions['continue code execution'] == false) {
        return false;
      } else {
        return true;
      }
    }

  }
?>